package com.googlecode.lazyrecords.berkeleydb;

import com.googlecode.lazyrecords.Definition;
import com.googlecode.lazyrecords.Keyword;
import com.googlecode.lazyrecords.Schema;
import com.googlecode.totallylazy.Closeables;
import com.googlecode.totallylazy.Function1;
import com.googlecode.totallylazy.Sequence;
import com.googlecode.totallylazy.Sequences;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.SecondaryConfig;
import com.sleepycat.je.SecondaryDatabase;

import java.io.Closeable;
import java.io.File;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Storage implements Closeable, Schema {
    private final Environment environment;
    private final Map<Definition, Table> databases = new ConcurrentHashMap<Definition, Table>();

    public Storage(File directory) {
        if(!directory.exists()) {
            directory.mkdirs();
        }
        EnvironmentConfig config = new EnvironmentConfig();
        config.setAllowCreate(true);
        config.setTransactional(true);
        environment = new Environment(directory, config);
    }

    public Table create(Definition definition) {
        DatabaseConfig config = new DatabaseConfig();
        config.setAllowCreate(true);
        config.setTransactional(true);
        Database database = environment.openDatabase(null, definition.name(), config);
        RecordTuple recordTuple = new RecordTuple();
        Sequence<SecondaryDatabase> secondaryDatabases = definition.fields().map(createSecondaryDatabase(database, recordTuple));
        Table table = new Table(database, secondaryDatabases);
        databases.put(definition, table);
        return table;
    }

    private Function1<Keyword<?>, SecondaryDatabase> createSecondaryDatabase(final Database database, final RecordTuple recordTuple) {
        return new Function1<Keyword<?>, SecondaryDatabase>() {
            @Override
            public SecondaryDatabase call(Keyword<?> field) throws Exception {
                SecondaryConfig config = new SecondaryConfig();
                config.setAllowCreate(true);
                config.setAllowPopulate(true);
                config.setSortedDuplicates(true);
                config.setTransactional(true);
                config.setKeyCreator(new KeywordKeyCreator(recordTuple, field));
                return environment.openSecondaryDatabase(null, field.name(), database, config);
            }
        };
    }

    public void close() {
        Sequences.sequence(databases.values()).each(Closeables.close());
        environment.close();
    }

    public JeTransaction newTransaction() {
        return new JeTransaction(environment);
    }

    public Table get(Definition definition) {
        return databases.get(definition);
    }

    @Override
    public void define(Definition definition) {
        create(definition);
    }

    @Override
    public boolean exists(Definition definition) {
        return environment.getDatabaseNames().contains(definition.name());
    }

    @Override
    public void undefine(Definition definition) {
        environment.removeDatabase(null, definition.name());
    }
}
